 /*******************************************************************************
  * Copyright (c) 2005 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.osgi.framework.internal.core;

 import java.security.*;
 import java.util.*;
 import org.osgi.service.condpermadmin.Condition;

 /**
  *
  * This security manager implements the ConditionalPermission processing for
  * OSGi. It is to be used with ConditionalPermissionAdmin.
  *
  */
 public class FrameworkSecurityManager extends SecurityManager {
     /*
      * This is super goofy, but we need to make sure that the CheckContext and
      * CheckPermissionAction classes load early. Otherwise, we run into problems later.
      */
     static {
         Class c;
         c = CheckPermissionAction.class;
         c = CheckContext.class;
         c.getName(); // to prevent compiler warnings
 }

     static class CheckContext {
         // A non zero depth indicates that we are doing a recursive permission check.
 ArrayList depthCondSets = new ArrayList(2);
         ArrayList accs = new ArrayList(2);
         ArrayList CondClassSet;

         public int getDepth() {
             return depthCondSets.size() - 1;
         }
     }

     /**
      * Adds an array of Condition[] for a ProtectionDomain.
      *
      * @param condSet the array of Condition[] for the ProtectionDomain.
      * @return true if the condSet was added. false if this is a recursive check
      * and ConditionalPermissions cannot be used.
      */
     boolean addConditionsForDomain(Condition condSet[][]) {
         CheckContext cc = (CheckContext) localCheckContext.get();
         if (cc == null) {
             // We are being invoked in a weird way. Perhaps the ProtectionDomain is
 // getting invoked directly.
 return false;
         }
         Vector condSets = (Vector) cc.depthCondSets.get(cc.getDepth());
         if (condSets == null) {
             condSets = new Vector(2);
             cc.depthCondSets.set(cc.getDepth(), condSets);
         }
         condSets.add(condSet);
         return true;
     }

     ThreadLocal localCheckContext = new ThreadLocal ();

     static class CheckPermissionAction implements PrivilegedAction {
         Permission perm;
         Object context;
         FrameworkSecurityManager fsm;

         CheckPermissionAction(FrameworkSecurityManager fsm, Permission perm, Object context) {
             this.fsm = fsm;
             this.perm = perm;
             this.context = context;
         }

         public Object run() {
             fsm.internalCheckPermission(perm, context);
             return null;
         }
     }

     public void checkPermission(Permission perm, Object context) {
         AccessController.doPrivileged(new CheckPermissionAction(this, perm, context));
     }

     /**
      * Gets the AccessControlContext currently being evaluated by
      * the SecurityManager.
      *
      * @return the AccessControlContext currently being evaluated by the SecurityManager, or
      * null if no AccessControlContext is being evaluted. Note: this method will
      * return null if the permission check is being done directly on the AccessControlContext
      * rather than the SecurityManager.
      */
     public AccessControlContext getContextToBeChecked() {
         CheckContext cc = (CheckContext) localCheckContext.get();
         if (cc != null && cc.accs != null && !cc.accs.isEmpty())
             return (AccessControlContext) cc.accs.get(cc.accs.size()-1);
         return null;
     }
     
     public void internalCheckPermission(Permission perm, Object context) {
         AccessControlContext acc = (AccessControlContext) context;
         CheckContext cc = (CheckContext) localCheckContext.get();
         if (cc == null) {
             cc = new CheckContext();
             localCheckContext.set(cc);
         }
         cc.depthCondSets.add(null); // initialize postponed condition set to null
 cc.accs.add(acc);
         try {
             acc.checkPermission(perm);
             // We want to pop the first set of postponed conditions and process them
 Vector remainingSets = (Vector) cc.depthCondSets.get(cc.getDepth());
             if (remainingSets != null) {
                 /*
                  * In this bit of code we have to try every possible combination
                  * of conditional permissions that still need to be evaluated. We
                  * do this using recursion to keep track of the different
                  * combinations we have tried. The top call will setup the different
                  * combinations for the first protection domain with unevaluated
                  * conditions. The final call will actually evaluate the condition.
                  * If a good combination is found, it will immediately bubble
                  * up.
                  */
                 Hashtable condContextDict = new Hashtable(2);
                 // The remainder we will process recursively.
 Condition conds[][] = (Condition[][]) remainingSets.remove(0);
                 for (int i = 0; i < conds.length; i++)
                     if (recursiveCheck(remainingSets, conds[i], null, condContextDict, cc))
                         return; // found a pass return without SecurityException
 throw new SecurityException ("Conditions not satisfied"); //$NON-NLS-1$
 }
         } finally {
             cc.depthCondSets.remove(cc.getDepth());
             cc.accs.remove(cc.accs.size()-1);
         }
     }

     /**
      * Checks that at least one combination of the Condition[] in the Vector can
      * be satisfied along with the passed array of Conditions.
      *
      * @param remainingSets the Vector of Condition[][] to check.
      * @param conditions the conditions that must be satisfied.
      * @param condDict a dictionary that will be filled with the Conditions to
      * check.
      * @param condContextDict a Dictionary of Dictionaries that will be passed
      * to Condition.isSatisfied when performing repeated check.
      * @param cc the CheckContext
      * @return true if a successful combination was found.
      */
     private boolean recursiveCheck(Vector remainingSets, Condition[] conditions, Hashtable condDict, Hashtable condContextDict, CheckContext cc) {
         // clone condDict and clone each Vector in the condDict
 if (condDict == null) {
             condDict = new Hashtable(2);
         } else {
             Hashtable copyCondDict = new Hashtable(2);
             for (Enumeration keys = condDict.keys(); keys.hasMoreElements();) {
                 Object key = keys.nextElement();
                 copyCondDict.put(key, ((Vector) condDict.get(key)).clone());
             }
             condDict = copyCondDict;
         }
         for (int i = 0; i < conditions.length; i++) {
             if (conditions[i] == null)
                 continue;
             Vector condList = (Vector) condDict.get(conditions[i].getClass());
             if (condList == null) {
                 condList = new Vector();
                 condDict.put(conditions[i].getClass(), condList);
             }
             condList.add(conditions[i]);
         }
         if (remainingSets.size() > 0) {
             Condition conds[][] = (Condition[][]) remainingSets.get(0);
             Vector newSets = (Vector) remainingSets.clone();
             newSets.remove(0);
             for (int i = 0; i < conds.length; i++)
                 if (recursiveCheck(newSets, conds[i], condDict, condContextDict, cc))
                     return true;
             return false;
         }
         Enumeration keys = condDict.keys();
         while (keys.hasMoreElements()) {
             Class key = (Class ) keys.nextElement();
             Vector conds = (Vector) condDict.get(key);
             if (conds.size() == 0)
                 continue; // This should never happen since we only add to the condDict if there is a condition
 Condition condArray[] = (Condition[]) conds.toArray(new Condition[conds.size()]);
             Dictionary context = (Dictionary) condContextDict.get(key);
             if (context == null) {
                 context = new Hashtable(2);
                 condContextDict.put(key, context);
             }
             if (cc.CondClassSet == null)
                 cc.CondClassSet = new ArrayList(2);
             if (cc.CondClassSet.contains(condArray[0].getClass()))
                 return false; // doing recursion into same condition class
 cc.CondClassSet.add(condArray[0].getClass());
             try {
                 if (!condArray[0].isSatisfied(condArray, context))
                     return false;
             } finally {
                 cc.CondClassSet.remove(condArray[0].getClass());
             }
         }
         return true;
     }

     public void checkPermission(Permission perm) {
         checkPermission(perm, getSecurityContext());
     }

     public Object getSecurityContext() {
         return AccessController.getContext();
     }
 }

